/*
 * Copyright (c) 2017-2019, Lindenis Tech. Ltd.
 * All rights reserved.
 *
 * File:
 *
 * Description:
 *
 * Author:
 *      xiaoshujun@lindeni.com
 *
 * Create Date:
 *      2020/10/13
 *
 * History:
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "osal_ringbuf.h"
#include "osal_log.h"

typedef struct _ringbuf_ctx_t
{
    uint8_t *       p_ring;
    unsigned int    i_total;
    unsigned int    i_free_size;
    unsigned int    i_read_offset;
    unsigned int    i_write_offset;
    lock_mutex      mutex;
} ringbuf_ctx_t;

_handle_t ringbuf_create(int size)
{
    ringbuf_ctx_t * p_ctx = malloc(sizeof(ringbuf_ctx_t));
    if (p_ctx == NULL)
    {
        return (_handle_t)NULL;
    }

    memset(p_ctx, 0, sizeof(ringbuf_ctx_t));
    p_ctx->i_total = size;
    p_ctx->i_free_size = size;
    p_ctx->p_ring = (uint8_t *)malloc(size);
    if (!p_ctx->p_ring)
    {
        loge("malloc ring buffer size(%d) failed", size);
        free(p_ctx);
        return (_handle_t)NULL;
    }

    init_lock(&p_ctx->mutex);

    return (_handle_t)p_ctx;
}

void ringbuf_destroy(_handle_t h_ring)
{
    ringbuf_ctx_t * p_ctx = (ringbuf_ctx_t *)h_ring;
    if (p_ctx != NULL)
    {
        if (p_ctx->p_ring != NULL)
        {
            free(p_ctx->p_ring);
            p_ctx->p_ring = NULL;
        }
        destroy_lock(&p_ctx->mutex);
        free(p_ctx);
        p_ctx = NULL;
    }
}

int ringbuf_read(_handle_t h_ring, uint8_t * data, int size)
{
    ringbuf_ctx_t * p_ctx = (ringbuf_ctx_t *)h_ring;
    int read_size = 0;
    int written_size = 0;
    uint8_t * p_read = NULL;

    if (p_ctx == NULL || data == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    do_lock(&p_ctx->mutex);
    if ((p_ctx->i_total - p_ctx->i_free_size) < size)
    {
        logw("read failed, available size: %d < %d, total: %d", p_ctx->i_total - p_ctx->i_free_size, size, p_ctx->i_total);
        return -1;
    }
    p_read = p_ctx->p_ring + p_ctx->i_read_offset;
    if (p_ctx->i_write_offset >= p_ctx->i_read_offset)
    {
        /*
         *      |<-----------------i_total-------------------->|
         *                  |<--size-->|
         *               or |<----------size-------->|
         *      ================================================
         *      |           |                    |
         *    p_ring   i_read_offset       i_write_offset
         */
        written_size = p_ctx->i_write_offset - p_ctx->i_read_offset;
        read_size = (written_size >= size) ? size : written_size;
        memcpy(data, p_read, read_size);
        p_ctx->i_read_offset += read_size;
    }
    else
    {
        int tail_size = p_ctx->i_total - p_ctx->i_read_offset;
        if (tail_size >= size)
        {
            /*
             *      |<-----------------i_total-------------------->|
             *                                       |<--size-->|
             *      ================================================
             *      |           |                    |
             *    p_ring  i_write_offset        i_read_offset
             */
            read_size = size;
            memcpy(data, p_read, read_size);
            p_ctx->i_read_offset += read_size;
        }
        else
        {
            /*
             *      |<-----------------i_total-------------------->|
             *                                       |<-tail_size->|
             *                                       |<-----size----
             *      --->|
             *      ================================================
             *      |           |                    |
             *    p_ring  i_write_offset        i_read_offset
             */
            written_size = tail_size + p_ctx->i_write_offset;
            read_size = (written_size >= size) ? size : written_size;
            memcpy(data, p_read, tail_size);
            memcpy(data + tail_size, p_ctx->p_ring, read_size - tail_size);
            p_ctx->i_read_offset = read_size - tail_size;
        }
    }
    p_ctx->i_free_size += read_size;
    do_unlock(&p_ctx->mutex);

    return read_size;
}

int ringbuf_write(_handle_t h_ring, uint8_t * data, int size)
{
    ringbuf_ctx_t * p_ctx = (ringbuf_ctx_t *)h_ring;
    int write_size = 0;
    int free_size = 0;
    uint8_t * p_write = NULL;

    if (p_ctx == NULL || data == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    do_lock(&p_ctx->mutex);
    if (p_ctx->i_free_size < size)
    {
        logw("write failed, free size: %d < %d, total: %d", p_ctx->i_free_size, size, p_ctx->i_total);
        do_unlock(&p_ctx->mutex);
        return -1;
    }
    p_write = p_ctx->p_ring + p_ctx->i_write_offset;
    if (p_ctx->i_read_offset <= p_ctx->i_write_offset)
    {
        int tail_size = p_ctx->i_total - p_ctx->i_write_offset;
        if (tail_size >= size)
        {
            /*
             *      |<-----------------i_total-------------------->|
             *                                       |<--size-->|
             *      ================================================
             *      |           |                    |
             *    p_ring   i_read_offset       i_write_offset
             */
            write_size = size;
            memcpy(p_write, data, write_size);
            p_ctx->i_write_offset += write_size;
        }
        else
        {
            /*
             *      |<-----------------i_total-------------------->|
             *                                       |<-tail_size->|
             *                                       |<-----size----
             *      --->|
             *      ================================================
             *      |           |                    |
             *    p_ring   i_read_offset       i_write_offset
             */
            free_size = p_ctx->i_total - (p_ctx->i_write_offset - p_ctx->i_read_offset);
            write_size = (free_size >= size) ? size : free_size;
            memcpy(p_write, data, tail_size);
            memcpy(p_ctx->p_ring, data + tail_size, write_size - tail_size);
            p_ctx->i_write_offset = write_size - tail_size;
        }
    }
    else
    {
        /*
         *      |<-----------------i_total-------------------->|
         *                  |<-----free_size---->|
         *      ================================================
         *      |           |                    |
         *    p_ring  i_write_offset        i_read_offset
         */
        free_size = p_ctx->i_read_offset - p_ctx->i_write_offset;
        write_size = (free_size >= size) ? size : free_size;
        memcpy(p_write, data, write_size);
        p_ctx->i_write_offset += write_size;
    }
    p_ctx->i_free_size -= write_size;
    do_unlock(&p_ctx->mutex);

    return write_size;
}

int ringbuf_get_free_size(_handle_t h_ring)
{
    ringbuf_ctx_t * p_ctx = (ringbuf_ctx_t *)h_ring;
    int free_size = 0;

    if (p_ctx == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    do_lock(&p_ctx->mutex);
    free_size = p_ctx->i_free_size;
    do_unlock(&p_ctx->mutex);

    return free_size;
}

int ringbuf_get_avail_size(_handle_t h_ring)
{
    ringbuf_ctx_t * p_ctx = (ringbuf_ctx_t *)h_ring;
    int avail_size = 0;

    if (p_ctx == NULL)
    {
        loge("error input parameters");
        return -1;
    }

    do_lock(&p_ctx->mutex);
    avail_size = p_ctx->i_total - p_ctx->i_free_size;
    do_unlock(&p_ctx->mutex);

    return avail_size;
}

void ringbuf_dump(_handle_t h_ring)
{
    ringbuf_ctx_t * p_ctx = (ringbuf_ctx_t *)h_ring;
    if (p_ctx == NULL)
    {
        loge("error input parameters");
        return;
    }

    logd("i_total: %d, i_read_offset: %d, i_write_offset: %d",
        p_ctx->i_total,
        p_ctx->i_read_offset,
        p_ctx->i_write_offset);
}

